The purpose of this script is to obtain data from the Home Station Database for Level Sender, in C:/Users/rpauloo/Documents/LevelSender/db/levelsender.sqlite. Ultimately, the goal is to develop an R Shiny Web application that automatically downloads the most up-to-date levelsender data and displays it.

Information from Solinist on LevelSender can be found here.

The R interface to SQLite is documented here, and offers far superior examples compared to anything else available on the web at the time of this writing.

First, let’s load the relevant libraries.

library(RSQLite)
library(DBI)
library(tidyverse)
library(stringr)

Refresh emails and append to sqlite database.

# simulates clicking the "Retrieve Emails Button"
system(
  shQuote(
    "C:/Program Files (x86)/Solinst/LevelSender/LSEmailClient.exe", 
    type = "cmd" # change to "sh" for Unix/bash, and "csh" for C-shell
  )
)

# opens the software which finalizes the data append from the emails that were read during the LSEmailClient.exe
system(
  shQuote(
    "C:/Program Files (x86)/Solinst/LevelSender/LSender.exe", 
    type = "cmd" # change to "sh" for Unix/bash, and "csh" for C-shell
  )
)

Second, let’s connect to our .sqlite database via R, and list the tables of data within.

# connect to SQLite database that Solinist regularly updates
db <- dbConnect(SQLite(), dbname = "C:/Users/rpauloo/Documents/LevelSender/db/levelsender.sqlite")

# list tables
db_tables <- dbListTables(db)

db_tables

For now, we’re interested in all of this data, so let’s read it all in a list, and explore.

# We can read in one table by simply naming it...
dbReadTable(db, "ConfigEmailSetup")
# ...or we can all the tables into a list...
table_list <- lapply(db_tables, function(x) { dbReadTable(db, x) } )
# ...and example them one-by-one.
table_list[[8]] 
# It looks like the "ReceivedEmail" table contains the groundwater level
dbReadTable(db, "ReceivedEmail")
# There's a lot of information that we don't need. Let's select what we do need.
dbReadTable(db, "ReceivedEmail") %>% 
  select(ReceivedDate, Subject, Body)

Looks like there are a lot of emails that aren’t actually reports. Let’s filter out the emails that are relevant to us, and give the columns more intuitive names.

# read, select interesting data, filter for relevant emails with data, and rename columns
d <- 
  dbReadTable(db, "ReceivedEmail") %>% 
  select(ReceivedDate, Subject, Body) %>% 
  filter( grepl("LS Report", Subject) ) %>% 
  rename(date = ReceivedDate, subject = Subject, body = Body)
# We want to arrange these emails by the date they were received, but first we need to convert the `Date` from a character vector to a `Date` object.
d$date <- as.POSIXct( strptime( d$date, "%Y-%m-%d %H:%M:%S" ) )
# Now we can arrange by date.
d %>% arrange(date)

Oh no! Although these data are arranged by time, we’re actaully looking at data from multiple wells! In the body of each observation is a serial number. Let’s extract that so we can arrange by it as well before we mine the body of each email for data.

The body of the emails is complete spaggetti. We need to organize these and write rules to extract the data we need.

# clean up one email 
strsplit(d$body[143], "\r\n")

Create a new 4 column dataframe (datetime, temp, level, id) for each email body, and store in a list. Keep in mind that some email bodies will have more than one level logger.

l <- unlist(strsplit(d$body[100], "\r\n")) # email body as character vector

# get MW serial # 
id <- l[str_detect(l, "Serial: ")][2] # 1st serial # == level sender
id <- as.numeric(substr(id, 9, nchar(id)))

# get all datetime, temp, level data
i <- str_detect(l, "^[:digit:]") # all lines that begin with a digit
ld <- l[i] 
ld

# organize into a dataframe
m <- str_split_fixed(ld, ", ", 3) # matrix of strings
m[, 2:3] <- round(as.numeric(m[, 2:3]), 2)
df <- as.data.frame(m)
colnames(df) <- c("dt", "temp", "level")
df$dt <- as.POSIXct( strptime(df$dt, format = '%d/%m/%Y %H:%M:%S') ) # format dates
df$id <-  id # add id
df

Generalize for all monitoring wells… but first check to see which records contain MW5

mw5 <- ": 283687" # serial number of MW5 (which includes barrologger)

# which records include MW5?
temp <- sapply(d$body, str_detect, mw5)
names(temp) <- NULL
#temp
sum(temp) # 182 records contain MW5

# number of serial numbers per email
temp2 <- lapply(d$body, function(x){unlist(strsplit(x, "\r\n"))} )
temp3 <- sapply(temp2, function(x){length(x[str_detect(x, "Serial: ")])})
data.frame(mw_5 = temp, n_serial = temp3) # visualize 
temp4 <- temp2[temp3==1] # Oneto Denir well doesn't have a serial number...
length(temp4) # sizteen records from Oneto Denir

Generalize for all monitoring wells.

v[str_detect(v, "Serial: ")]
[1] "Serial: 284221"  "Serial: 2083805"
LS0tDQp0aXRsZTogIlVzaW5nIFJTUUxpdGUgdG8gcXVlcnkgTGV2ZWxTZW5kZXIiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCmF1dGhvcjogUmljaCBQYXVsb28gJiBBbXkgWW9kZXINCmRhdGU6IEZlYnJ1YXJ5IDIxLCAyMDE4DQotLS0NCg0KVGhlIHB1cnBvc2Ugb2YgdGhpcyBzY3JpcHQgaXMgdG8gb2J0YWluIGRhdGEgZnJvbSB0aGUgSG9tZSBTdGF0aW9uIERhdGFiYXNlIGZvciBMZXZlbCBTZW5kZXIsIGluIGBDOi9Vc2Vycy9ycGF1bG9vL0RvY3VtZW50cy9MZXZlbFNlbmRlci9kYi9sZXZlbHNlbmRlci5zcWxpdGVgLiBVbHRpbWF0ZWx5LCB0aGUgZ29hbCBpcyB0byBkZXZlbG9wIGFuIFIgU2hpbnkgV2ViIGFwcGxpY2F0aW9uIHRoYXQgYXV0b21hdGljYWxseSBkb3dubG9hZHMgdGhlIG1vc3QgdXAtdG8tZGF0ZSBsZXZlbHNlbmRlciBkYXRhIGFuZCBkaXNwbGF5cyBpdC4gIA0KDQpJbmZvcm1hdGlvbiBmcm9tIFNvbGluaXN0IG9uIExldmVsU2VuZGVyIGNhbiBiZSBmb3VuZCBbaGVyZV0oaHR0cHM6Ly93d3cuc29saW5zdC5jb20vcHJvZHVjdHMvZGF0YWxvZ2dlcnMtYW5kLXRlbGVtZXRyeS9zb2xpbnN0LXRlbGVtZXRyeS1zeXN0ZW1zLzk1MDAtbGV2ZWxzZW5kZXIvaW5zdHJ1Y3Rpb25zL3VzZXItZ3VpZGUvNS0zLTAtZGF0YS1mcm9tLXNvZnR3YXJlLnBocCM1LTQtMCkuICANCg0KVGhlIFIgaW50ZXJmYWNlIHRvIFNRTGl0ZSBpcyBkb2N1bWVudGVkIFtoZXJlXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvUlNRTGl0ZS9SU1FMaXRlLnBkZiksIGFuZCBvZmZlcnMgZmFyIHN1cGVyaW9yIGV4YW1wbGVzIGNvbXBhcmVkIHRvIGFueXRoaW5nIGVsc2UgYXZhaWxhYmxlIG9uIHRoZSB3ZWIgYXQgdGhlIHRpbWUgb2YgdGhpcyB3cml0aW5nLiAgDQoNCg0KRmlyc3QsIGxldCdzIGxvYWQgdGhlIHJlbGV2YW50IGxpYnJhcmllcy4NCmBgYHtyfQ0KbGlicmFyeShSU1FMaXRlKQ0KbGlicmFyeShEQkkpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoc3RyaW5ncikNCmBgYA0KDQpSZWZyZXNoIGVtYWlscyBhbmQgYXBwZW5kIHRvIHNxbGl0ZSBkYXRhYmFzZS4NCmBgYHtyfQ0KIyBzaW11bGF0ZXMgY2xpY2tpbmcgdGhlICJSZXRyaWV2ZSBFbWFpbHMgQnV0dG9uIg0Kc3lzdGVtKA0KICBzaFF1b3RlKA0KICAgICJDOi9Qcm9ncmFtIEZpbGVzICh4ODYpL1NvbGluc3QvTGV2ZWxTZW5kZXIvTFNFbWFpbENsaWVudC5leGUiLCANCiAgICB0eXBlID0gImNtZCIgIyBjaGFuZ2UgdG8gInNoIiBmb3IgVW5peC9iYXNoLCBhbmQgImNzaCIgZm9yIEMtc2hlbGwNCiAgKQ0KKQ0KDQojIG9wZW5zIHRoZSBzb2Z0d2FyZSB3aGljaCBmaW5hbGl6ZXMgdGhlIGRhdGEgYXBwZW5kIGZyb20gdGhlIGVtYWlscyB0aGF0IHdlcmUgcmVhZCBkdXJpbmcgdGhlIExTRW1haWxDbGllbnQuZXhlDQpzeXN0ZW0oDQogIHNoUXVvdGUoDQogICAgIkM6L1Byb2dyYW0gRmlsZXMgKHg4NikvU29saW5zdC9MZXZlbFNlbmRlci9MU2VuZGVyLmV4ZSIsIA0KICAgIHR5cGUgPSAiY21kIiAjIGNoYW5nZSB0byAic2giIGZvciBVbml4L2Jhc2gsIGFuZCAiY3NoIiBmb3IgQy1zaGVsbA0KICApDQopDQpgYGANCg0KDQpTZWNvbmQsIGxldCdzIGNvbm5lY3QgdG8gb3VyIC5zcWxpdGUgZGF0YWJhc2UgdmlhIFIsIGFuZCBsaXN0IHRoZSB0YWJsZXMgb2YgZGF0YSB3aXRoaW4uDQpgYGB7cn0NCiMgY29ubmVjdCB0byBTUUxpdGUgZGF0YWJhc2UgdGhhdCBTb2xpbmlzdCByZWd1bGFybHkgdXBkYXRlcw0KZGIgPC0gZGJDb25uZWN0KFNRTGl0ZSgpLCBkYm5hbWUgPSAiQzovVXNlcnMvcnBhdWxvby9Eb2N1bWVudHMvTGV2ZWxTZW5kZXIvZGIvbGV2ZWxzZW5kZXIuc3FsaXRlIikNCg0KIyBsaXN0IHRhYmxlcw0KZGJfdGFibGVzIDwtIGRiTGlzdFRhYmxlcyhkYikNCg0KZGJfdGFibGVzDQpgYGANCg0KRm9yIG5vdywgd2UncmUgaW50ZXJlc3RlZCBpbiBhbGwgb2YgdGhpcyBkYXRhLCBzbyBsZXQncyByZWFkIGl0IGFsbCBpbiBhIGxpc3QsIGFuZCBleHBsb3JlLg0KYGBge3J9DQojIFdlIGNhbiByZWFkIGluIG9uZSB0YWJsZSBieSBzaW1wbHkgbmFtaW5nIGl0Li4uDQpkYlJlYWRUYWJsZShkYiwgIkNvbmZpZ0VtYWlsU2V0dXAiKQ0KDQojIC4uLm9yIHdlIGNhbiBhbGwgdGhlIHRhYmxlcyBpbnRvIGEgbGlzdC4uLg0KdGFibGVfbGlzdCA8LSBsYXBwbHkoZGJfdGFibGVzLCBmdW5jdGlvbih4KSB7IGRiUmVhZFRhYmxlKGRiLCB4KSB9ICkNCg0KIyAuLi5hbmQgZXhhbXBsZSB0aGVtIG9uZS1ieS1vbmUuDQp0YWJsZV9saXN0W1s4XV0gDQoNCiMgSXQgbG9va3MgbGlrZSB0aGUgIlJlY2VpdmVkRW1haWwiIHRhYmxlIGNvbnRhaW5zIHRoZSBncm91bmR3YXRlciBsZXZlbA0KZGJSZWFkVGFibGUoZGIsICJSZWNlaXZlZEVtYWlsIikNCg0KIyBUaGVyZSdzIGEgbG90IG9mIGluZm9ybWF0aW9uIHRoYXQgd2UgZG9uJ3QgbmVlZC4gTGV0J3Mgc2VsZWN0IHdoYXQgd2UgZG8gbmVlZC4NCmRiUmVhZFRhYmxlKGRiLCAiUmVjZWl2ZWRFbWFpbCIpICU+JSANCiAgc2VsZWN0KFJlY2VpdmVkRGF0ZSwgU3ViamVjdCwgQm9keSkNCmBgYA0KDQpMb29rcyBsaWtlIHRoZXJlIGFyZSBhIGxvdCBvZiBlbWFpbHMgdGhhdCBhcmVuJ3QgYWN0dWFsbHkgcmVwb3J0cy4gTGV0J3MgZmlsdGVyIG91dCB0aGUgZW1haWxzIHRoYXQgYXJlIHJlbGV2YW50IHRvIHVzLCBhbmQgZ2l2ZSB0aGUgY29sdW1ucyBtb3JlIGludHVpdGl2ZSBuYW1lcy4NCmBgYHtyfQ0KIyByZWFkLCBzZWxlY3QgaW50ZXJlc3RpbmcgZGF0YSwgZmlsdGVyIGZvciByZWxldmFudCBlbWFpbHMgd2l0aCBkYXRhLCBhbmQgcmVuYW1lIGNvbHVtbnMNCmQgPC0gDQogIGRiUmVhZFRhYmxlKGRiLCAiUmVjZWl2ZWRFbWFpbCIpICU+JSANCiAgc2VsZWN0KFJlY2VpdmVkRGF0ZSwgU3ViamVjdCwgQm9keSkgJT4lIA0KICBmaWx0ZXIoIGdyZXBsKCJMUyBSZXBvcnQiLCBTdWJqZWN0KSApICU+JSANCiAgcmVuYW1lKGRhdGUgPSBSZWNlaXZlZERhdGUsIHN1YmplY3QgPSBTdWJqZWN0LCBib2R5ID0gQm9keSkNCg0KIyBXZSB3YW50IHRvIGFycmFuZ2UgdGhlc2UgZW1haWxzIGJ5IHRoZSBkYXRlIHRoZXkgd2VyZSByZWNlaXZlZCwgYnV0IGZpcnN0IHdlIG5lZWQgdG8gY29udmVydCB0aGUgYERhdGVgIGZyb20gYSBjaGFyYWN0ZXIgdmVjdG9yIHRvIGEgYERhdGVgIG9iamVjdC4NCmQkZGF0ZSA8LSBhcy5QT1NJWGN0KCBzdHJwdGltZSggZCRkYXRlLCAiJVktJW0tJWQgJUg6JU06JVMiICkgKQ0KDQojIE5vdyB3ZSBjYW4gYXJyYW5nZSBieSBkYXRlLg0KZCAlPiUgYXJyYW5nZShkYXRlKQ0KYGBgDQoNCk9oIG5vISBBbHRob3VnaCB0aGVzZSBkYXRhIGFyZSBhcnJhbmdlZCBieSB0aW1lLCB3ZSdyZSBhY3RhdWxseSBsb29raW5nIGF0IGRhdGEgZnJvbSBtdWx0aXBsZSB3ZWxscyEgSW4gdGhlIGJvZHkgb2YgZWFjaCBvYnNlcnZhdGlvbiBpcyBhIHNlcmlhbCBudW1iZXIuIExldCdzIGV4dHJhY3QgdGhhdCBzbyB3ZSBjYW4gYXJyYW5nZSBieSBpdCBhcyB3ZWxsIGJlZm9yZSB3ZSBtaW5lIHRoZSBib2R5IG9mIGVhY2ggZW1haWwgZm9yIGRhdGEuDQoNCg0KVGhlIGJvZHkgb2YgdGhlIGVtYWlscyBpcyBjb21wbGV0ZSBzcGFnZ2V0dGkuIFdlIG5lZWQgdG8gb3JnYW5pemUgdGhlc2UgYW5kIHdyaXRlIHJ1bGVzIHRvIGV4dHJhY3QgdGhlIGRhdGEgd2UgbmVlZC4NCmBgYHtyfQ0KIyBjbGVhbiB1cCBvbmUgZW1haWwgDQpzdHJzcGxpdChkJGJvZHlbMTQzXSwgIlxyXG4iKQ0KYGBgDQoNCkNyZWF0ZSBhIG5ldyA0IGNvbHVtbiBkYXRhZnJhbWUgKGRhdGV0aW1lLCB0ZW1wLCBsZXZlbCwgaWQpIGZvciBlYWNoIGVtYWlsIGJvZHksIGFuZCBzdG9yZSBpbiBhIGxpc3QuIEtlZXAgaW4gbWluZCB0aGF0IHNvbWUgZW1haWwgYm9kaWVzIHdpbGwgaGF2ZSBtb3JlIHRoYW4gb25lIGxldmVsIGxvZ2dlci4NCmBgYHtyfQ0KbCA8LSB1bmxpc3Qoc3Ryc3BsaXQoZCRib2R5WzEwMF0sICJcclxuIikpICMgZW1haWwgYm9keSBhcyBjaGFyYWN0ZXIgdmVjdG9yDQoNCiMgZ2V0IE1XIHNlcmlhbCAjIA0KaWQgPC0gbFtzdHJfZGV0ZWN0KGwsICJTZXJpYWw6ICIpXVsyXSAjIDFzdCBzZXJpYWwgIyA9PSBsZXZlbCBzZW5kZXINCmlkIDwtIGFzLm51bWVyaWMoc3Vic3RyKGlkLCA5LCBuY2hhcihpZCkpKQ0KDQojIGdldCBhbGwgZGF0ZXRpbWUsIHRlbXAsIGxldmVsIGRhdGENCmkgPC0gc3RyX2RldGVjdChsLCAiXls6ZGlnaXQ6XSIpICMgYWxsIGxpbmVzIHRoYXQgYmVnaW4gd2l0aCBhIGRpZ2l0DQpsZCA8LSBsW2ldIA0KbGQNCg0KIyBvcmdhbml6ZSBpbnRvIGEgZGF0YWZyYW1lDQptIDwtIHN0cl9zcGxpdF9maXhlZChsZCwgIiwgIiwgMykgIyBtYXRyaXggb2Ygc3RyaW5ncw0KbVssIDI6M10gPC0gcm91bmQoYXMubnVtZXJpYyhtWywgMjozXSksIDIpDQpkZiA8LSBhcy5kYXRhLmZyYW1lKG0pDQpjb2xuYW1lcyhkZikgPC0gYygiZHQiLCAidGVtcCIsICJsZXZlbCIpDQpkZiRkdCA8LSBhcy5QT1NJWGN0KCBzdHJwdGltZShkZiRkdCwgZm9ybWF0ID0gJyVkLyVtLyVZICVIOiVNOiVTJykgKSAjIGZvcm1hdCBkYXRlcw0KZGYkaWQgPC0gIGlkICMgYWRkIGlkDQpkZg0KYGBgDQoNCkdlbmVyYWxpemUgZm9yIGFsbCBtb25pdG9yaW5nIHdlbGxzLi4uIGJ1dCBmaXJzdCBjaGVjayB0byBzZWUgd2hpY2ggcmVjb3JkcyBjb250YWluIE1XNQ0KYGBge3J9DQptdzUgPC0gIjogMjgzNjg3IiAjIHNlcmlhbCBudW1iZXIgb2YgTVc1ICh3aGljaCBpbmNsdWRlcyBiYXJyb2xvZ2dlcikNCg0KIyB3aGljaCByZWNvcmRzIGluY2x1ZGUgTVc1Pw0KdGVtcCA8LSBzYXBwbHkoZCRib2R5LCBzdHJfZGV0ZWN0LCBtdzUpDQpuYW1lcyh0ZW1wKSA8LSBOVUxMDQojdGVtcA0Kc3VtKHRlbXApICMgMTgyIHJlY29yZHMgY29udGFpbiBNVzUNCg0KIyBudW1iZXIgb2Ygc2VyaWFsIG51bWJlcnMgcGVyIGVtYWlsDQp0ZW1wMiA8LSBsYXBwbHkoZCRib2R5LCBmdW5jdGlvbih4KXt1bmxpc3Qoc3Ryc3BsaXQoeCwgIlxyXG4iKSl9ICkNCnRlbXAzIDwtIHNhcHBseSh0ZW1wMiwgZnVuY3Rpb24oeCl7bGVuZ3RoKHhbc3RyX2RldGVjdCh4LCAiU2VyaWFsOiAiKV0pfSkNCmRhdGEuZnJhbWUobXdfNSA9IHRlbXAsIG5fc2VyaWFsID0gdGVtcDMpICMgdmlzdWFsaXplIA0KdGVtcDQgPC0gdGVtcDJbdGVtcDM9PTFdICMgT25ldG8gRGVuaXIgd2VsbCBkb2Vzbid0IGhhdmUgYSBzZXJpYWwgbnVtYmVyLi4uDQpsZW5ndGgodGVtcDQpICMgc2l6dGVlbiByZWNvcmRzIGZyb20gT25ldG8gRGVuaXINCmBgYA0KDQpHZW5lcmFsaXplIGZvciBhbGwgbW9uaXRvcmluZyB3ZWxscy4NCmBgYHtyfQ0KIyBzdHJhdCB3aXRoIHRlbXAyLCB0aGUgZm9ybWF0dGVkIGxpbmVzIG9mIGVhY2ggZW1haWwgYm9keQ0KbGluZXMgPC0gbGFwcGx5KGQkYm9keSwgZnVuY3Rpb24oeCl7dW5saXN0KHN0cnNwbGl0KHgsICJcclxuIikpfSApDQoNCiMgZnVuY3Rpb24gdG8gYXBwbHkNCmdldF9kYXRhIDwtIGZ1bmN0aW9uKHYpeyANCiAgDQogICN2ICA9IGxpbmVzW1sxXV0NCiAgI3YyID0gbGluZXNbWzIwMDBdXQ0KICBzcyA8LSBzdW0oc3RyX2RldGVjdCh2LCBtdzUpKSAjIGRvZXMgbXc1IGFwcGVhciBpbiB0aGUgZW1haWw/DQogIA0KICAjIGlmIHRoZSB3ZWxsIGlzIE1XIDUsIHdoY2loIGNvbnRhaW5zIGEgYmFycm9sb2dnZXINCiAgaWYgKHNzID09IDApIHsNCiAgICBpZCA8LSB2W3N0cl9kZXRlY3QodiwgIlNlcmlhbDogIildWzJdICMgMm5kIHNlcmlhbCBpcyBiX2xvZ2dlciwgbm90IExTDQogIH0NCg0KICAjIGlmIHRoZSB3ZWxsIGRvZXMgbm90IGNvbnRhaW4gYSBiYXJyb2xvZ2dlcg0KICBpZiAoc3MgPT0gMSkgew0KICAgIGlkIDwtIHZbc3RyX2RldGVjdCh2LCAiU2VyaWFsOiAiKV1bM10gIyBvciBpcyBpdCBbMl0/PyBBc2sgQW5kcmV3Li4uDQogIH0NCg0KDQogIA0KICAjIGdldCBNVyBzZXJpYWwgIyANCiAgaWQgPC0gbFtzdHJfZGV0ZWN0KGwsICJTZXJpYWw6ICIpXVsyXSAjIDFzdCBzZXJpYWwgIyA9PSBsZXZlbCBzZW5kZXINCiAgaWQgPC0gYXMubnVtZXJpYyhzdWJzdHIoaWQsIDksIG5jaGFyKGlkKSkpDQogIA0KICAjIGdldCBhbGwgZGF0ZXRpbWUsIHRlbXAsIGxldmVsIGRhdGENCiAgaSA8LSBzdHJfZGV0ZWN0KGwsICJeWzpkaWdpdDpdIikgIyBhbGwgbGluZXMgdGhhdCBiZWdpbiB3aXRoIGEgZGlnaXQNCiAgbGQgPC0gbFtpXSANCiAgbGQNCiAgDQogICMgb3JnYW5pemUgaW50byBhIGRhdGFmcmFtZQ0KICBtIDwtIHN0cl9zcGxpdF9maXhlZChsZCwgIiwgIiwgMykgIyBtYXRyaXggb2Ygc3RyaW5ncw0KICBtWywgMjozXSA8LSByb3VuZChhcy5udW1lcmljKG1bLCAyOjNdKSwgMikNCiAgZGYgPC0gYXMuZGF0YS5mcmFtZShtKQ0KICBjb2xuYW1lcyhkZikgPC0gYygiZHQiLCAidGVtcCIsICJsZXZlbCIpDQogIGRmJGR0IDwtIGFzLlBPU0lYY3Qoc3RycHRpbWUoZGYkZHQsIGZvcm1hdCA9ICclZC8lbS8lWSAlSDolTTolUycpKSAjIGZvcm1hdCBkYXRlcw0KICBkZiRpZCA8LSAgaWQgIyBhZGQgaWQNCiAgZGYNCiAgDQogIA0KfQ0KDQpnZXRfZGF0YShkJGJvZHkpDQpgYGANCg0KDQoNCg0KDQoNCg0K